(This discussion is relevant to many UNIXes besides linux.) >> Perhaps a "{}" on the command line should be sub'ed with the relative >> name and a "{{}}" should be sub'ed with the absolute name. > I agree that find's syntax would have to be (and should) be extended. I do not think find should be changed, but the rm command should be replaced with something more suitable. The following perl script may be useful. Paul Szabo - System Manager // School of Mathematics and Statistics psz@maths.usyd.edu.au // University of Sydney, NSW 2006, Australia ----- #! /usr/local/bin/perl -- # #V safe-rm V1.0 27 May 96 Paul Szabo <psz@maths.usyd.edu.au> # # Safe rm program to be used in root cron jobs like # find /tmp -type f -atime +2 -exec safe-rm {} \; # instead of rm, to ensure that the path does not contain any symlinks. # # # There is a race between when find starts to descend into /tmp and when it # # calls rm. Suppose I make deeply nested trees like # # # # /tmp/a/a/a/a/a/a/passwd (all real dirs and file) and also # # /tmp/b/a/a/a/a/a -> /etc (all real dirs and the last symlink) # # # # then, after find starts up but before it reaches /tmp/a/.../passwd I do # # # # cd /tmp; mv a c; mv b a # # # # then find will exec 'rm /tmp/a/a/.../a/passwd' but this removes /etc/passwd. # # If the directories are deep enough then find will slow down a lot, and the # # race will be easy to win. # # If using safe-rm then we can also try to remove empty directories: # find /tmp -atime +2 -exec safe-rm {} \; if ( -d '/usr/apollo' ) { $apollo = 1; } ( $CMD = $0 ) =~ s!^/?([^/]*/)*!!; sub err { if ("$USAGE" ne '') { if ($#_ >= 0) { print "$CMD failed with error:\n\n"; } else { print "$CMD failed with some unknown error.\n"; } } foreach (@_) { print "$_\n"; } if ("$USAGE" ne '') { print "\nUsage:$USAGE"; } exit 1; } # Returns success or failure whether path given is acceptable sub goodpath { my ($path) = @_; if ( length($path) < 1 || length($path) > 999 ) { return 0; } if ( $path =~ m![^a-zA-Z0-9/.,:_-]! ) { return 0; } if ( $path =~ m!^[^a-zA-Z0-9/.]! ) { return 0; } if ( $apollo ) { if ( $path =~ m!/[^a-zA-Z0-9/.]! ) { return 0; }; if ( $path =~ m!.//! ) { return 0; } } else { if ( $path =~ m!/[^a-zA-Z0-9.]! ) { return 0; } } if ( $path =~ m![^/]/$! ) { return 0; } return 1; } # Returns full (absolute) path beginning with /, or error message. # Could be simplified for safe-rm: only need to ensure that we got # a path starting with / and check for symlinks. sub fullpath { # Whinge: Why is this not part of standard Perl? # Or at least why is getwd not implemented? my ($path) = @_; my ($obj, $dir, $nam, $top, $loop, @statp, @statt, @statd, @stato); if ( $apollo ) { $top = '//'; } else { $top = '/'; } goodpath($path) || return "Bad pathname $path ."; @statp = stat("$path"); $#statp = 1; if ( ! -e _ ) { return "Object $path does not exist"; } $obj = "$path"; if ( $obj =~ m![^/]/$! ) { $obj =~ s!/$!!; } ( $dir = "$obj" ) =~ s![^/]*$!!; ( $nam = "$obj" ) =~ s!^.*/!!; if ( "$obj" ne "$dir$nam" ) { return "Cannot decompose object name $obj: $dir and $nam ?"; } lstat("$obj"); $loop = 0; while ( -l _ ) { $loop++; if ( $loop > 20 ) { return "Symlink loop in $obj"; } $nam = readlink("$obj"); if ("$nam" eq '') { return "Cannot resolve link $obj: $!"; } $obj = "$dir$nam"; goodpath($obj) || return "Bad object name $obj ."; ( $dir = "$obj" ) =~ s![^/]*$!!; ( $nam = "$obj" ) =~ s!^.*/!!; if ( "$obj" ne "$dir$nam" ) { return "Cannot decompose object name $obj: $dir and $nam ?"; } @stato = stat("$obj"); $#stato = 1; if ( "@statp" ne "@stato" ) { return "Cannot resolve $path: not same as $obj ?"; } lstat("$obj"); } if ( "$nam" eq '.' || "$nam" eq '..' ) { $dir = "$dir$nam"; $nam = ''; } @statt = stat("$top"); $#statt = 1; if ( ! -d _ ) { return "But $top is not a directory ?"; } if ("$dir" eq '') { $dir = '.'; } if ( $dir =~ m![^/]/$! ) { $dir =~ s!/$!!; } @statd = stat("$dir"); $#statd = 1; $loop = 0; while ( "@statd" ne "@statt" ) { if ( $loop > 100 ) { return "Directory loop in $obj"; } if ( ! -d _ ) { return "But $dir is not a directory ?"; } opendir (DH,"$dir/..") || return "Cannot read directory $dir/.. ?"; @stato = (); while ( "@statd" ne "@stato" ) { $name = readdir(DH) || last; goodpath("$dir/../$name") || next; @stato = lstat("$dir/../$name"); $#stato = 1; } if ( "@statd" ne "@stato" ) { return "Cannot look up $dir (for $dir/$nam) in $dir/.. ?"; } closedir (DH) || return "Cannot stop reading directory $dir/.. ?"; $dir = "$dir/.."; if ( "$nam" eq '' ) { $nam = "$name"; } else { $nam = "$name/$nam"; } goodpath($nam) || return "Bad name $dir/$nam ."; @statd = stat("$dir"); $#statd = 1; if ( "@statd" eq "@stato" ) { last; } } $obj = "$top$nam"; goodpath($obj) || return "Bad final pathname $obj"; @stato = stat("$obj"); $#stato = 1; if ( "@statp" ne "@stato" ) { return "Cannot resolve $path: not same as $obj ?"; } return "$obj"; } if ( $#ARGV != 0 ) { err ("Specify one object (only) to remove."); } ($FILE) = @ARGV; # These checks are somewhat redundant goodpath($FILE) || err ("Bad object name $FILE ."); @STATFILE = lstat("$FILE"); if ( ! -e _ ) { err ("File $FILE does not exist."); } if ( -l _ ) { err ("Object $FILE is a symbolic link."); } if ( ! -d _ && ! -f _ ) { err ("Object $FILE is not a (plain) file or a directory."); } $FULLPATH = fullpath("$FILE"); if ( $FULLPATH !~ m!^/! ) { err ("Error resolving $FILE:", " $FULLPATH"); } if ( "$FULLPATH" ne "$FILE" ) { err ("Not full pathname $FILE :", " it really is $FULLPATH"); } # Some more redundancy @STATFULL = lstat("$FILE"); if ("@STATFILE" ne "@STATFULL") { err ("Error resolving $FILE : seems to have changed."); } if ( -f _ ) { # print "About to unlink $FILE ...\n"; unlink "$FILE" || err ("Cannot remove file $FILE"); } elsif ( -d _ ) { # print "About to rmdir $FILE ...\n"; rmdir "$FILE"; # || err ("Cannot remove dir $FILE"); # No error message: it may have been not empty } else { err ("Object $FILE is not a (plain) file nor a directory."); } #!#